package net.sf.ivyide.ivy;

import net.sf.ivyide.ModuleId;
import net.sf.ivyide.ModuleRevisionId;
import org.apache.ivy.Ivy;
import org.apache.ivy.core.cache.DefaultRepositoryCacheManager;
import org.apache.ivy.core.cache.RepositoryCacheManager;
import org.apache.ivy.core.module.descriptor.Artifact;
import org.apache.ivy.core.module.descriptor.DefaultArtifact;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.module.id.*;
import org.apache.ivy.core.report.ResolveReport;
import org.apache.ivy.core.repository.RepositoryManagementEngine;
import org.apache.ivy.core.resolve.DownloadOptions;
import org.apache.ivy.core.resolve.IvyNode;
import org.apache.ivy.core.resolve.ResolveOptions;
import org.apache.ivy.core.retrieve.RetrieveEngine;
import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
import org.apache.ivy.plugins.resolver.*;
import net.sf.ivyide.*;

import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.text.ParseException;
import java.util.*;

// Referenced classes of package com.ivyplugin.j2meplugin.module:
//            ProjectStructure, DependencyType, InvalidFileException, IvyModuleSettings

public class IvyResolvedModule implements ResolvedModule {
    private Map<ModuleRevisionId, List<Artifact>> m_moduleArtifacts;
    private ResolveReport m_report;
    private ModuleId m_moduleId;
    private final Ivy m_ivy;
    private final MyAntEnvironmentInfo m_environmentInfo;

    public String toString() {
        return m_moduleId.toString();
    }

    public ModuleId getModuleId() {
        return m_moduleId;
    }


    public IvyResolvedModule(File moduleDir, Ivy ivy, MyAntEnvironmentInfo myAntEnvironmentInfo) {
        m_ivy = ivy;
        m_environmentInfo = myAntEnvironmentInfo;
        resolve(ivy, moduleDir, myAntEnvironmentInfo);
    }


    private Artifact[] getDependencyArtifacts(Ivy ivy, URL ivyFileURL, MyAntEnvironmentInfo environmentInfo) throws IOException, ParseException {

        String[] configurations = m_environmentInfo.getIvyConfigurations().split(",");
        ResolveOptions options = new ResolveOptions();
        options.setUseCacheOnly(false);
        options.setTransitive(true);
        options.setValidate(true);
        options.setConfs(configurations);
        options.setRevision(null);
        options.setArtifactFilter(environmentInfo.getArtifactFilter());

        m_report = ivy.resolve(ivyFileURL, options);
        m_moduleId = convertModuleRevisionId(m_report.getModuleDescriptor().getModuleRevisionId()).getModuleId();
        List<Artifact> artifacts1 = m_report.getArtifacts();
        return artifacts1.toArray(new Artifact[artifacts1.size()]);
    }

    private ModuleRevisionId convertModuleRevisionId(org.apache.ivy.core.module.id.ModuleRevisionId id) {
        return new ModuleRevisionId(new ModuleId(id.getOrganisation(), id.getName()), id.getRevision());
    }

    private void resolve(Ivy ivy, File moduleDir, MyAntEnvironmentInfo environmentInfo) {
        try {
            URI ivyFileUrl = new File(moduleDir, "ivy.xml").toURI();
            // Map<ModuleId, File> modules = environmentInfo.getModules();
            Artifact artifacts[] = getDependencyArtifacts(ivy, ivyFileUrl.toURL(), environmentInfo);
            m_moduleArtifacts = new LinkedHashMap<ModuleRevisionId, List<Artifact>>();
            for (Artifact artifact : artifacts) {
                ModuleRevisionId moduleRevisionId = convertModuleRevisionId(artifact.getModuleRevisionId());
                List<Artifact> moduleArtifacts = m_moduleArtifacts.get(moduleRevisionId);
                if (moduleArtifacts == null) {
                    moduleArtifacts = new LinkedList<Artifact>();
                    m_moduleArtifacts.put(moduleRevisionId, moduleArtifacts);
                }
                moduleArtifacts.add(artifact);
            }
        } catch (ParseException e) {
            //Should not occur
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public Collection<ModuleRevisionId> getDependencies() {
        return m_moduleArtifacts.keySet();
    }

    private DefaultRepositoryCacheManager getDefaultCacheManager() {
        return (DefaultRepositoryCacheManager) m_ivy.getSettings().getDefaultRepositoryCacheManager();
    }
    //should this be in manager instead ??
    public LibraryDescriptor getLibraryDescriptor(ModuleRevisionId moduleRevisionId) {
        List<Artifact> artifactList = m_moduleArtifacts.get(moduleRevisionId);
        Collection<File> classes = new LinkedList<File>();
        Collection<File> sources = new LinkedList<File>();
        Collection<File> docs = new LinkedList<File>();
        String[] sourceConfigurations = m_environmentInfo.getIvySourceConfigurations().split(",");
        String[] docConfigurations = m_environmentInfo.getIvyJavadocConfigurations().split(",");
        XmlModuleDescriptorParser parser = XmlModuleDescriptorParser.getInstance();
        if (sourceConfigurations.length > 0 || docConfigurations.length > 0) {
            final ModuleDescriptor descriptor;
            try {
                //No need to resolve ??
                //m_ivy.resolve(moduleRevisionId, new String[confs], false, false, m_ivy.getDefaultCache(), null, false, false, null);
                File ivyFileInCache = getDefaultCacheManager().getIvyFileInCache(new org.apache.ivy.core.module.id.ModuleRevisionId(new org.apache.ivy.core.module.id.ModuleId(moduleRevisionId.getOrganisation(), moduleRevisionId.getName()), moduleRevisionId.getRevision()));
                descriptor = parser.parseDescriptor(m_ivy.getSettings(), ivyFileInCache.toURL(), false);
            } catch (ParseException e) {
                throw new RuntimeException(e);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            addDeclaredMetas(sources, sourceConfigurations, descriptor);
            addDeclaredMetas(docs, docConfigurations, descriptor);
        }
        List<Artifact> classArtifacts = new LinkedList<Artifact>();
        for (Artifact artifact : artifactList) {
            File archiveFileInCache = getDefaultCacheManager().getArchiveFileInCache(artifact);
            classes.add(archiveFileInCache);
            classArtifacts.add(artifact);
        }
        if (sources.isEmpty()) {
            checkForUndeclaredMetas(sources, classArtifacts, "source", "sources");
        }
        if (docs.isEmpty()) {
            checkForUndeclaredMetas(docs, classArtifacts, "javadoc", "javadoc");
        }
        return new LibraryDescriptorImpl(classes.toArray(new File[classes.size()]), sources.toArray(new File[sources.size()]), docs.toArray(new File[docs.size()]));
    }

    private void addDeclaredMetas(Collection<File> files, String[] configurations, ModuleDescriptor descriptor) {
        for (String conf : configurations) {
            Artifact[] artifacts = descriptor.getArtifacts(conf);
            for (Artifact artifact : artifacts) {
                //typically, source are not downloaded during'normal' resolve, so we still have to download them..
                m_ivy.getResolveEngine().download(artifact, new DownloadOptions());
                File archiveFileInCache = getDefaultCacheManager().getArchiveFileInCache(artifact);
                files.add(archiveFileInCache);
            }
        }
    }

    private void checkForUndeclaredMetas(Collection<File> result, List<Artifact> classArtifacts, String metaType,
                                         String metaClassifier) {
        for (Artifact classArtifact : classArtifacts) {
            File path = getMetaArtifactPath(classArtifact, metaType, metaClassifier);
            if (path != null) {
                result.add(path);
            }
        }
    }

    private File getMetaArtifactPath(Artifact artifact, String metaType,
                                     String metaClassifier) {
        DependencyResolver resolver = m_ivy.getSettings().getResolver(artifact.getModuleRevisionId());
        if (isMetaRetrievalSupportedByResolver(resolver)) {
            Map<String, String> extraAtt = new HashMap<String, String>(artifact.getExtraAttributes());
            extraAtt.put("classifier", metaClassifier);
            Artifact metaArtifact = new DefaultArtifact(artifact.getModuleRevisionId(), artifact
                    .getPublicationDate(), artifact.getName(), metaType, "jar", extraAtt);

            File metaArtifactFile = getDefaultCacheManager().getArchiveFileInCache(metaArtifact);
            File attempt = new File(metaArtifactFile.getAbsolutePath() + ".notfound");
            if (metaArtifactFile.exists()) {
                return metaArtifactFile;
            } else if (attempt.exists()) {
                return null;
            } else {
                DownloadOptions options = new DownloadOptions();
                resolver.download(new Artifact[]{metaArtifact}, options);
                File archiveFileInCache = getDefaultCacheManager().getArchiveFileInCache(metaArtifact);
                if (archiveFileInCache.exists()) {
                    return archiveFileInCache;
                } else {
                    //tried it, but not found. Don't try next time..
                    try {
                        attempt.getParentFile().mkdirs();
                        attempt.createNewFile();
                    } catch (IOException e) {
                        throw new RuntimeException("unable to write attempt file in cache", e);
                    }
                }
            }

        }
        return null;
    }

    private boolean isMetaRetrievalSupportedByResolver(DependencyResolver resolver) {
        if (resolver instanceof URLResolver) {
            URLResolver urlResolver = (URLResolver) resolver;
            if (urlResolver.isM2compatible()) {
                List<String> patterns = urlResolver.getArtifactPatterns();
                for (String pattern : patterns) {
                    if (pattern.contains("classifier")) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    //this method should throw an exception in case this module did not resolve
    //successfully
    public void checkResolveErrors() {
        ResolveReport report = m_report;
        if (report.hasError()) {
            final StringBuffer buffer = new StringBuffer();
            buffer.append(new StringBuffer("Errors occured during resolution of component " + m_moduleId + "\n"));
            if (report.getUnresolvedDependencies().length > 0) {
                IvyNode[] unresolvedDependencies = report.getUnresolvedDependencies();
                buffer.append("\n");
                buffer.append(unresolvedDependencies.length > 1 ? "Unresolved dependencies:\n" : "Unresolved dependency:\n");
                for (IvyNode unresolvedDependency : unresolvedDependencies) {
                    buffer.append(unresolvedDependency).append(": ").append(unresolvedDependency.getProblemMessage()).append("\n");
                }
            }
            buffer.append("\nMake sure the project can be resolved in order to get all dependencies.");
            throw new ResolveException(buffer.toString());
        }

    }
}